package com.topcycle.tlight;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Color;
import android.os.IBinder;
import android.os.Process;
import android.util.Log;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Calendar;

public class NetController {
	public static final String RECV_DATA_EXTRA = "recv_data_extra";
	public static final String RECV_DATA_ACTION = "recv_data_action";

	public static final String ACTION_SEND_SCHEDUAL_CMD = "send_schedual_cmd";
	public static final String KEY_SCHEDUAL_CMD = "key_sche_cmd";
	public static final String KEY_SCHEDUAL_IS_LED_ON = "key_sche_is_led_on";
	public static final String KEY_SCHEDUAL_CMD_DEV_INDEX = "key_sche_cmd_dev_index";

	private static final int PORT = 43708;

	private static final byte CMD_IP_MSG    =   0x01;
	private static final byte CMD_IP_MSG_RESPONSE   =  0x02;
	private static final byte CMD_CONFIRM_NODE = 0x03;
	private static final byte CMD_SWITCH_MODE    = 0x04;
	private static final byte CMD_NODE_ORDER  = 0x05;
	private static final byte CMD_INIT_NON_CHILD = 0x06;
	private static final byte CMD_INIT_NON_CHILD_CONFIRM = 0x07;
	private static final byte CMD_DEL_NODE = 0x08;
	private static final byte CMD_LIGHT_ON = 0x11;
	private static final byte CMD_LIGHT_OFF = 0x12;
	private static final byte CMD_LIGHT_DEGREE = 0x13;
	private static final byte CMD_LIGHT_ARGB = 0x14;
	private static final byte device_find[] = {0x50,(byte) 0xE0,0x4E,(byte) 0xA1,(byte) 0xEA,0x2C,0x38,0x6C};//top-cycle md5 16
	private static final byte device_find_response[] = {0x6C,0x38,0x2C,(byte)0xEA,(byte)0xA1,0x4E,(byte)0xE0,0x50};


	private static final String TAG = "NetController";

	private static AlarmManager mAlarmManager;

	private static Context mContext;
	private static Device mCurrentDevice;

	private static DatagramSocket mSocket;
	private static InetAddress mInetAddress;

//	private static String mRemoteIp;

	public static void init(Context context) {
		mContext = context;
		mAlarmManager = (AlarmManager) context
				.getSystemService(Context.ALARM_SERVICE);
		createSocket();
		startUDPRecv(context);
		updateAllDevIp();
	}

	public static void updateAllDevIp() {
		for(int i=0;i<DeviceManager.getDeviceListSize();i++) {
			updateIpByMac(DeviceManager.getDeviceByPos(i).mac);
		}
	}

	private static void updateIpByMac(byte[] mac) {
		byte[] bts = new byte[7];
		bts[0] = CMD_IP_MSG;
		System.arraycopy(mac,0,bts,1,6);
		sendBroadcastMsg(bts);
	}

	public static void sendLightOnCmd() {
		if (DeviceManager.getCurrentDevice() == null) {
			return;
		}
		byte[] bts = new byte[7];
		bts[0] = CMD_LIGHT_ON;
		System.arraycopy(DeviceManager.getCurrentDevice().mac, 0, bts, 1, 6);
		sendMsg(bts);
	}

	public static void sendLightOffCmd() {
		if (DeviceManager.getCurrentDevice() == null) {
			return;
		}
		byte[] bts = new byte[7];
		bts[0] = CMD_LIGHT_OFF;
		System.arraycopy(DeviceManager.getCurrentDevice().mac,0,bts,1,6);
		sendMsg(bts);
	}

	public static void sendLightDegreeUpdateCmd(boolean isColorWheel) {
		if (DeviceManager.getCurrentDevice() == null) {
			return;
		}
		byte[] bts = new byte[12];
		bts[0] = CMD_LIGHT_DEGREE;
		System.arraycopy(DeviceManager.getCurrentDevice().mac,0,bts,1,6);
		byte degree = (byte) (DeviceManager.getCurrentDevice().light);
		int color = DeviceManager.getCurrentDevice().color;
		if (isColorWheel) {
			bts[8] = 0;
			bts[7] = 0;
		}
		else {
			bts[8] = (byte) ((byte) ((color >>> 24) * 0.01f * degree) & 0xff);
			bts[7] = (byte) ((byte) ((255 - (color >>> 24)) * 0.01f * degree) & 0xff);
		}
		bts[9] = (byte) ((byte) (Color.red(color) * 0.01f * degree) & 0xff);
		bts[10] = (byte) ((byte) (Color.green(color) * 0.01f * degree) & 0xff);
		bts[11] = (byte) ((byte) (Color.blue(color)* 0.01f * degree) & 0xff);
		sendMsg(bts);
	}

	public static void sendSwitchModeCmd() {
		if (DeviceManager.getCurrentDevice() == null) {
			return;
		}
		byte[] bts = new byte[50];
		bts[0] = CMD_SWITCH_MODE;
		System.arraycopy(DeviceManager.getCurrentDevice().mac,0,bts,1,6);
		sendMsg(bts);
	}

	public static void sendLightARGBUpdateCmd() {
		if (DeviceManager.getCurrentDevice() == null) {
			return;
		}
		byte[] bts = new byte[12];
		bts[0] = CMD_LIGHT_ARGB;
		System.arraycopy(DeviceManager.getCurrentDevice().mac,0,bts,1,6);
		byte degree = (byte) (DeviceManager.getCurrentDevice().light);
		int color = DeviceManager.getCurrentDevice().color;
		bts[7] = 0;
		bts[8] = 0;
		bts[9] = (byte) ((byte) (Color.red(color) * 0.01f * degree) & 0xff);
		bts[10] = (byte) ((byte) (Color.green(color) * 0.01f * degree) & 0xff);
		bts[11] = (byte) ((byte) (Color.blue(color)* 0.01f * degree) & 0xff);
		sendMsg(bts);
	}

	public static void sendDelLedCmd() {
		byte[] bts = new byte[8];
		bts[0] = CMD_DEL_NODE;
		System.arraycopy(DeviceManager.getCurrentDevice().mac,0,bts,1,6);
		sendMsg(bts);
	}

	public static void scheduleCmd(boolean isLedOn, int hour, int minute) {
		Device device = DeviceManager.getCurrentDevice();
		if (device == null) {
			return;
		}
		Intent intent = new Intent(ACTION_SEND_SCHEDUAL_CMD);
		byte bts[] = {  isLedOn ? CMD_LIGHT_ON : CMD_LIGHT_OFF, device.mac[0], device.mac[1], device.mac[2], device.mac[3], device.mac[4],device.mac[5] };
		intent.putExtra(KEY_SCHEDUAL_CMD, bts);
		intent.putExtra(KEY_SCHEDUAL_CMD_DEV_INDEX, DeviceManager.getCurrentPos());
		intent.putExtra(KEY_SCHEDUAL_IS_LED_ON, isLedOn);
		// use device.index as pending intent request code
		device.devClock.pIntent = PendingIntent.getBroadcast(mContext,
				DeviceManager.getCurrentPos(), intent, PendingIntent.FLAG_ONE_SHOT);
		Calendar calendar = Calendar.getInstance();
		calendar.set( hour, minute, 0);

		Log.i(TAG, "schedule time in millis: " + calendar.getTimeInMillis());
		mAlarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
				device.devClock.pIntent);
	}
/*
	public static void scheduleCmd(boolean isLedOn, int year, int month,
								   int day, int hour, int minute) {
		Device device = DeviceManager.getCurrentDevice();
		if (device == null) {
			return;
		}
		device.devClock.year = year;
		device.devClock.month = month;
		device.devClock.day = day;
		device.devClock.hour = hour;
		device.devClock.minute = minute;
		device.devClock.isOn = isLedOn;
		Intent intent = new Intent(ACTION_SEND_SCHEDUAL_CMD);
		byte bts[] = { (byte) device.index,
				isLedOn ? CMD_LIGHT_ON : CMD_LIGHT_OFF, (byte) 0xF0, 0x00,
				0x00, 0x00 };
		intent.putExtra(KEY_SCHEDUAL_CMD, bts);
		intent.putExtra(KEY_SCHEDUAL_CMD_DEV_INDEX, device.index);
		intent.putExtra(KEY_SCHEDUAL_IS_LED_ON, isLedOn);
		// use device.index as pending intent request code
		device.devClock.pIntent = PendingIntent.getBroadcast(mContext,
				device.index, intent, PendingIntent.FLAG_ONE_SHOT);
		Calendar calendar = Calendar.getInstance();
		calendar.set(year, month, day, hour, minute, 0);

		Log.i(TAG, "schedule time in millis: " + calendar.getTimeInMillis());
		mAlarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
				device.devClock.pIntent);
	}

*/

	public static void excuteScheduleCmd(byte bts[]) {
		sendMsg(bts);
	}


	public static void findDevice() {
		byte[] bts = device_find;
		sendBroadcastMsg(bts);
	}


	private static void startUDPRecv(Context context) {
		Intent intent = new Intent();
		intent.setClass(context, DataDealService.class);
		context.startService(intent);
	}

	private static void createSocket() {
		try {
			mSocket = new DatagramSocket(null);
			mSocket.setReuseAddress(true);
			mSocket.bind(new InetSocketAddress(PORT));
		} catch (SocketException e) {
			Log.e(TAG, "createSocket-" + e.toString());
		}
	}

	public  static void setInetAddress(byte[] ip) {
		try {
			mInetAddress = InetAddress.getByAddress(ip);

		} catch (UnknownHostException e) {
			Log.e(TAG, "setInetAddress-" + e.toString());
		}
	}

	private static void sendMsg(final byte[] data) {
		if (data == null) {
			Log.e(TAG, "msg == NULL!!!");
			return;
		}
		if (mSocket == null) {
			Log.e(TAG, "socket have not create, invoke createSocket() method!");
			return;
		}
		if (mInetAddress == null) {
			Log.e(TAG,
					"server address have not set, invoke setInetAddress() method first!");
			return;
		}

		new Thread(new Runnable() {

			@Override
			public void run() {
				try {
					DatagramPacket packet = new DatagramPacket(data,
							data.length, mInetAddress, PORT);
					mSocket.send(packet);
					broadcastCmdSent(data);
					Log.i(TAG, "sendMsg...");
					System.out.println(Utils.bytesToHexString(data,0,data.length));
				} catch (IOException e) {
					Log.e(TAG, "sendMsg-" + e.toString());
				}
			}
		}).start();
	}

	private static void sendBroadcastMsg(final byte[] data) {
		if (data == null) {
			Log.e(TAG, "msg == NULL!!!");
			return;
		}
		if (mSocket == null) {
			Log.e(TAG,
					"socket have not create, invoke createBroadcastSocket() method!");
			return;
		}

		new Thread(new Runnable() {

			@Override
			public void run() {
				try {
					Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
					mSocket.setBroadcast(true);
					DatagramPacket packet = new DatagramPacket(data,
							data.length, InetAddress
							.getByName("255.255.255.255"), PORT);
					mSocket.send(packet);
					broadcastCmdSent(data);
				} catch (IOException e) {
					Log.e(TAG, "sendBroadcastMsg-" + e.toString());
				} finally {
					try {
						mSocket.setBroadcast(false);
					} catch (SocketException e) {
						e.printStackTrace();
					}
				}
			}
		}).start();
	}

	private static void broadcastCmdSent(byte[] cmd) {
		Intent i = new Intent(MainActivity.ACTION_UPDATE_CMD_TEXTVIEW);
		i.putExtra(MainActivity.KEY_NAME_CMD, cmd);
		mContext.sendBroadcast(i);
	}

	private static void broadcastUpdateDeviceList() {
		Intent i = new Intent(MainActivity.ACTION_UPDATE_DEVICE_SPINNER);
		mContext.sendBroadcast(i);
	}

	private static void broadcastRenameNewDevice(byte[] mac, byte[] ip){
		Intent i = new Intent(MainActivity.ACTION_RENAME_NEW_DEVICE);
		i.putExtra(MainActivity.KEY_RENAME_DEVICE_MAC, mac);
		i.putExtra(MainActivity.KEY_DEVICE_IP,ip);
		mContext.sendBroadcast(i);
	}

	private static void broadcastInitialBox(){
		Intent i = new Intent(MainActivity.ACTION_INITIAL_BOX);
		mContext.sendBroadcast(i);
	}

	private static void broadcastInitialBoxFinished(){
		Intent i = new Intent(MainActivity.ACTION_INITIAL_BOX_OK);
		mContext.sendBroadcast(i);
	}

	private static void broadcastNoNewDevice(){
		Intent i = new Intent(MainActivity.ACTION_NO_NEW_DEVICE);
		mContext.sendBroadcast(i);
	}


	public static class DataDealService extends Service {
		private TimerBroadcastReceiver mTimerBroadcastReceiver;

		@Override
		public IBinder onBind(Intent intent) {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public void onCreate() {
			super.onCreate();

			mTimerBroadcastReceiver = new TimerBroadcastReceiver();
			IntentFilter timerFilter = new IntentFilter();
			timerFilter.addAction(ACTION_SEND_SCHEDUAL_CMD);
			registerReceiver(mTimerBroadcastReceiver, timerFilter);

			Thread thread = new Thread(new Runnable() {
				byte data[] = new byte[128];
				int length = 0;
				DatagramPacket packet = new DatagramPacket(data, data.length);

				@Override
				public void run() {
					while (true) {
						try {
							if (mSocket == null) {
								continue;
							}
							mSocket.receive(packet);
							data = packet.getData();
							length = packet.getLength();
							dealData();
						} catch (Exception e) {
							Log.e(TAG, "DataRecvService-" + e.toString());
						}
					}
				}

				private void dealData() {
					Log.i(TAG,"data recev:"+Utils.bytesToHexString(data,0,length));
					byte tmp[] = new byte[8];
					System.arraycopy(data, 0, tmp, 0, tmp.length);
					if (Arrays.equals(tmp, device_find_response)) {
						byte devMac[] = new byte[6];
						System.arraycopy(data, 8, devMac, 0, devMac.length);
						if (DeviceManager.getDeviceByMac(devMac) == null) {
							byte devIp[] = new byte[4];
							System.arraycopy(data, 8 + 6, devIp, 0, devIp.length);
							Log.i(TAG, "recv remote ip: " + Utils.bytesToHexString(devIp,0,devIp.length));
							broadcastRenameNewDevice(devMac, devIp);
							setInetAddress(devIp);
						}
					} else if (data[0] == CMD_IP_MSG_RESPONSE){
						byte devMac[] = new byte[6];
						System.arraycopy(data, 1, devMac, 0, devMac.length);
						byte devIp[] = new byte[4];
						System.arraycopy(data, 1 + 6, devIp, 0, devIp.length);
						if (devIp[0] == 0 ) return;
						DeviceManager.updateIpByMac(mContext, devMac, devIp);
						Log.i(TAG,Utils.bytesToHexString(devMac,3,3)+" IP updated: "+ Utils.bytesToHexString(devIp,0,devIp.length));
					}
				}
			});
			thread.start();
		}

		class TimerBroadcastReceiver extends BroadcastReceiver {

			@Override
			public void onReceive(Context context, Intent intent) {

				String action = intent.getAction();
				if (ACTION_SEND_SCHEDUAL_CMD.equals(action)) {
					byte cmd[] = intent.getByteArrayExtra(KEY_SCHEDUAL_CMD);
					int index = intent.getIntExtra(KEY_SCHEDUAL_CMD_DEV_INDEX,
							-1);
					boolean isOn = intent.getBooleanExtra(
							KEY_SCHEDUAL_IS_LED_ON, false);
					NetController.excuteScheduleCmd(cmd);

					Device device = DeviceManager.getDeviceByPos(index);
					DeviceManager.resetTimer(device.mac);
					device.isOn = isOn;
					if (index == DeviceManager.getCurrentPos()) {
						Intent i = new Intent(
								MainActivity.ACTION_UPDATE_TIMER_TEXTVIEW);
						sendBroadcast(i);
					}
				}
			}
		}
	}
}
